# Copyright (C) 2005-2015 Free Software Foundation, Inc.

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with GCC; see the file COPYING3.  If not see
# <http://www.gnu.org/licenses/>.

# DejaGnu test driver around Mike Cowlishaw's testsuite for decimal
# decimal arithmetic ("decTest").  See:
#    <http://www2.hursley.ibm.com/decimal/dectest.html>.
#
# Contributed by Ben Elliston <bje@au.ibm.com>.

set DEC_TORTURE_OPTIONS [list {} -O1 -O2 -O3 -Os -msoft-float]

proc target-specific-flags {} {
  set result "-frounding-math "
  return $result
}
  
# Load support procs (borrow these from c-torture).
load_lib c-torture.exp
load_lib target-supports.exp
load_lib torture-options.exp

# Skip these tests for targets that don't support this extension.
if { ![check_effective_target_dfp] } {
    return
}

# The list format is [coefficient, max-exponent, min-exponent].
set properties(_Decimal32) [list 7 96 -95]
set properties(_Decimal64) [list 16 384 -383]
set properties(_Decimal128) [list 34 6144 -6143]

# Operations implemented by the compiler.
set operators(add) {+}
set operators(compare) {==}
set operators(divide) {/}
set operators(multiply) {*}
set operators(subtract) {-}
set operators(minus) {-}
set operators(plus) {+}
set operators(apply) {}

# Operations imlemented by the library.
set libfuncs(abs) fabsl
set libfuncs(squareroot) sqrtl
set libfuncs(max) fmaxl
set libfuncs(min) fminl
set libfuncs(quantize) quantize
set libfuncs(samequantum) samequantum
set libfuncs(power) powl
set libfuncs(toSci) unknown
set libfuncs(tosci) unknown
set libfuncs(toEng) unknown
set libfuncs(toeng) unknown
set libfuncs(divideint) unknown
set libfuncs(rescale) unknown
set libfuncs(remainder) unknown
set libfuncs(remaindernear) unknown
set libfuncs(normalize) unknown
set libfuncs(tointegral) unknown
set libfuncs(trim) unknown

# Run all of the tests listed in TESTCASES by invoking df-run-test on
# each.  Skip tests that not included by the user invoking runtest
# with the foo.exp=test.c syntax.

proc dfp-run-tests { testcases } {
    global runtests
    foreach test $testcases {
	# If we're only testing specific files and this isn't one of
	# them, skip it.
	if ![runtest_file_p $runtests $test] continue
	dfp-run-test $test
    }
}

# Run a single test case named by TESTCASE.
# Called for each test by dfp-run-tests.

proc dfp-run-test { testcase } {
    set fd [open $testcase r]
    while {[gets $fd line] != -1} {
	switch -regexp -- $line {
	    {^[ \t]*--.*$} {
		# Ignore comments.
	    }
	    {^[ \t]*$} {
		# Ignore blank lines.
	    }
	    {^[ \t]*[^:]*:[^:]*} {
		regsub -- {[ \t]*--.*$} $line {} line
		process-directive $line
	    }
	    default {
		process-test-case $testcase $line
	    }
	}
    }
    close $fd
}

# Return the appropriate constant from <fenv.h> for MODE.

proc c-rounding-mode { mode } { 
    switch [string tolower $mode] {
	"floor"		{ return 0 } # FE_DEC_DOWNWARD
	"half_even"	{ return 1 } # FE_DEC_TONEARESTFROMZERO
	"half_up"	{ return 2 } # FE_DEC_TONEAREST
	"down"		{ return 3 } # FE_DEC_TOWARDZERO
	"ceiling"	{ return 4 } # FE_DEC_UPWARD
    }
    error "unsupported rounding mode ($mode)"
}

# Return a string of C code that forms the preamble to perform the
# test named ID.

proc c-test-preamble { id } {
    append result "/* Machine generated test case for $id */\n"
    append result "\n"
    append result "\#include <assert.h>\n"
    append result "\#include <fenv.h>\n"
    append result "\#include <math.h>\n"
    append result "\n"
    append result "int main ()\n"
    append result "\{"
    return $result
}

# Return a string of C code that forms the postable to the test named ID.

proc c-test-postamble { id } {
    return "\}"
}

# Generate a C unary expression that applies OPERATION to OP.

proc c-unary-expression {operation op} {
    global operators
    global libfuncs
    if [catch {set result "$operators($operation) $op"}] {
	# If operation isn't in the operators or libfuncs arrays,
	# we'll throw an error.  That's what we want.
	# FIXME: append d32, etc. here.
	set result "$libfuncs($operation) ($op)"
    }
    return $result
}

# Generate a C binary expression that applies OPERATION to OP1 and OP2.

proc c-binary-expression {operation op1 op2} {
    global operators
    global libfuncs
    if [catch {set result "$op1 $operators($operation) $op2"}] {
	# If operation isn't in the operators or libfuncs arrays,
	# we'll throw an error.  That's what we want.
	set result "$libfuncs($operation) ($op1, $op2)"
    }
    return $result
}

# Return the most appropriate C type (_Decimal32, etc) for this test.

proc c-decimal-type { } {
    global directives
    if [catch {set precision $directives(precision)}] {
	set precision "_Decimal128"
    }  
    if { $precision == 7 } {
	set result "_Decimal32"
    } elseif {$precision == 16} {
	set result "_Decimal64"
    } elseif {$precision == 34} {
	set result "_Decimal128"
    } else {
	error "Unsupported precision"
    }
    return $result
}

# Return the size of the most appropriate C type, in bytes.

proc c-sizeof-decimal-type { } {
    switch [c-decimal-type] {
	"_Decimal32"    { return 4 }
	"_Decimal64"    { return 8 }
	"_Decimal128"   { return 16 }
    }
    error "Unsupported precision"
}

# Return the right literal suffix for CTYPE.

proc c-type-suffix { ctype } {
    switch $ctype {
        "_Decimal32"   { return "df" }
        "_Decimal64"   { return "dd" }
        "_Decimal128"  { return "dl" }
        "float"        { return "f" }
	"long double"  { return "l" }
    }
    return ""
}

proc nan-p { operand } {
    if {[string match "NaN*" $operand] || [string match "-NaN*" $operand]} {
	return 1
    } else {
	return 0
    }
}

proc infinity-p { operand } {
    if {[string match "Inf*" $operand] || [string match "-Inf*" $operand]} {
	return 1
    } else {
	return 0
    }
}

proc isnan-builtin-name { } {
    set bits [expr [c-sizeof-decimal-type] * 8]
    return "__builtin_isnand$bits"
}

proc isinf-builtin-name { } {
    set bits [expr [c-sizeof-decimal-type] * 8]
    return "__builtin_isinfd$bits"
}

# Return a string that declares a C union containing the decimal type
# and an unsigned char array of the right size.

proc c-union-decl { } {
    append result "  union {\n"
    append result "    [c-decimal-type] d;\n"
    append result "    unsigned char bytes\[[c-sizeof-decimal-type]\];\n"
    append result "  } u;"
    return $result
}

proc transform-hex-constant {value} {
    regsub \# $value {} value
    regsub -all (\.\.) $value {0x\1, } bytes
    return [list $bytes]
}

# Create a C program file (named using ID) containing a test for a
# binary OPERATION on OP1 and OP2 that expects RESULT and CONDITIONS.

proc make-c-test {testcase id operation result conditions op1 {op2 "NONE"}} {
    global directives
    set filename ${id}.c
    set outfd [open $filename w]

    puts $outfd [c-test-preamble $id]
    puts $outfd [c-union-decl]
    if {[string compare $result ?] != 0} {
	if {[string index $result 0] == "\#"} {
	    puts $outfd "  static unsigned char compare\[[c-sizeof-decimal-type]\] = [transform-hex-constant $result];"
	}
    }
    if {[string compare $op2 NONE] == 0} {
	if {[string index $op1 0] == "\#"} {
	    puts $outfd "  static unsigned char fill\[[c-sizeof-decimal-type]\] = [transform-hex-constant $op1];"
	}
    }

    puts $outfd ""
    puts $outfd "  /*  FIXME: Set rounding mode with fesetround() once in libc.  */"
    puts $outfd "  __dfp_set_round ([c-rounding-mode $directives(rounding)]);"
    puts $outfd ""

    # Build the expression to be tested.
    if {[string compare $op2 NONE] == 0} {
	if {[string index $op1 0] == "\#"} {
	    puts $outfd "  memcpy (u.bytes, fill, [c-sizeof-decimal-type]);"
	} else {
	    puts $outfd "  u.d = [c-unary-expression $operation [c-operand $op1]];"
	}
    } else {
	puts $outfd "  u.d = [c-binary-expression $operation [c-operand $op1] [c-operand $op2]];"
    }

    # Test the result.
    if {[string compare $result ?] != 0} {
	# Not an undefined result ..
	if {[string index $result 0] == "\#"} {
	    # Handle hex comparisons.
	    puts $outfd "  return memcmp (u.bytes, compare, [c-sizeof-decimal-type]);"
	} elseif {[nan-p $result]} {
	    puts $outfd "  return ![isnan-builtin-name] (u.d);"
	} elseif {[infinity-p $result]} {
	    puts $outfd "  return ![isinf-builtin-name] (u.d);"
	} else {
	    # Ordinary values.
	    puts $outfd "  return !(u.d == [c-operand $result]);"
	}
    } else {
	puts $outfd "  return 0;"
    }

    puts $outfd [c-test-postamble $id]
    close $outfd
    return $filename
}

# Is the test supported for this target?

proc supported-p { id op } {
    global directives
    global libfuncs

    # Ops that are unsupported.  Many of these tests fail because they
    # do not tolerate the C front-end rounding the value of floating
    # point literals to suit the type of the constant.  Otherwise, by
    # treating the `apply' operator like C assignment, some of them do
    # pass.
    switch -- $op {
	apply		{ return 0 }
    }

    # Ditto for the following miscellaneous tests.
    switch $id {
	addx1130	{ return 0 }
	addx1131	{ return 0 }
	addx1132	{ return 0 }
	addx1133	{ return 0 }
	addx1134	{ return 0 }
	addx1135	{ return 0 }
	addx1136	{ return 0 }
	addx1138	{ return 0 }
	addx1139	{ return 0 }
	addx1140	{ return 0 }
	addx1141	{ return 0 }
	addx1142	{ return 0 }
	addx1151	{ return 0 }
	addx1152	{ return 0 }
	addx1153	{ return 0 }
	addx1154	{ return 0 }
	addx1160	{ return 0 }
	addx690		{ return 0 }
	mulx263		{ return 0 }
	subx947		{ return 0 }
    }

    if [info exist libfuncs($op)] {
	# No library support for now.
	return 0
    }
    if [catch {c-rounding-mode $directives(rounding)}] {
	# Unsupported rounding mode.
	return 0
    }
    if [catch {c-decimal-type}] {
	# Unsupported precision.
	return 0
    }
    return 1
}

# Break LINE into a list of tokens.  Be sensitive to quoting.
# There has to be a better way to do this :-|

proc tokenize { line } {
    set quoting 0
    set tokens [list]

    foreach char [split $line {}] {
	if {!$quoting} {
	    if { [info exists token] && $char == " " } {
		if {[string compare "$token" "--"] == 0} {
		    # Only comments remain.
		    return $tokens
		}
		lappend tokens $token
		unset token
	    } else {
		if {![info exists token] && $char == "'" } {
		    set quoting 1
		} else {
		    if { $char != " " } {
			append token $char
		    }
		}
	    }
	} else {
	    # Quoting.
	    if { $char == "'" } {
		set quoting 0
		if [info exists token] {
		    lappend tokens $token
		    unset token
		} else {
		    lappend tokens {}
		}
	    } else {
		append token $char
	    }
	}
    }
    # Flush any residual token.
    if {[info exists token] && [string compare $token "--"]} {
	lappend tokens $token
    }
    return $tokens
}

# Process a directive in LINE.

proc process-directive { line } {
    global directives
    set keyword [string tolower [string trim [lindex [split $line :] 0]]]
    set value [string tolower [string trim [lindex [split $line :] 1]]]
    set directives($keyword) $value
}

# Produce a C99-valid floating point literal.

proc c-operand {operand} {
    set bits [expr 8 * [c-sizeof-decimal-type]]

    switch -glob -- $operand {
	"Inf*"		{ return "__builtin_infd${bits} ()" }
	"-Inf*"		{ return "- __builtin_infd${bits} ()" }
    	"NaN*"		{ return "__builtin_nand${bits} (\"\")" }
	"-NaN*"		{ return "- __builtin_nand${bits} (\"\")" }
	"sNaN*"		{ return "__builtin_nand${bits} (\"\")" }
	"-sNaN*"	{ return "- __builtin_nand${bits} (\"\")" }
    }

    if {[string first . $operand] < 0 && \
	    [string first E $operand] < 0 && \
	    [string first e $operand] < 0} {
	append operand .
    }
    set suffix [c-type-suffix [c-decimal-type]]
    return [append operand $suffix]
}

# Process an arithmetic test in LINE from TESTCASE.

proc process-test-case { testcase line } {
    set testfile [file tail $testcase]

    # Compress multiple spaces down to one.
    regsub -all {  *} $line { } line

    set args [tokenize $line]
    if {[llength $args] < 5} {
	error "Skipping invalid test: $line"
	return
    }
    
    set id [string trim [lindex $args 0]]
    set operation [string trim [lindex $args 1]]
    set operand1 [string trim [lindex $args 2]]
    
    if { [string compare [lindex $args 3] -> ] == 0 } {
	# Unary operation.
	set operand2 NONE
	set result_index 4
	set cond_index 5
    } else {
	# Binary operation.
	set operand2 [string trim [lindex $args 3]]
	if { [string compare [lindex $args 4] -> ] != 0 } {
	    warning "Skipping invalid test: $line"
	    return
	}
	set result_index 5
	set cond_index 6
    }

    set result [string trim [lindex $args $result_index]]
    set conditions [list]
    for { set i $cond_index } { $i < [llength $args] } { incr i } {
	lappend conditions [string tolower [lindex $args $i]]
    }
    
    # If this test is unsupported, say so.
    if ![supported-p $id $operation] {
	unsupported "$testfile ($id)"
	return
    }

    if {[string compare $operand1 \#] == 0 || \
	    [string compare $operand2 \#] == 0} {
	unsupported "$testfile ($id), null reference"
 	return
    }

    # Construct a C program and then compile/execute it on the target.
    # Grab some stuff from the c-torture.exp test driver for this.

    set cprog [make-c-test $testfile $id $operation $result $conditions $operand1 $operand2]
    c-torture-execute $cprog [target-specific-flags]
}

### Script mainline:

if [catch {set testdir $env(DECTEST)}] {
    # If $DECTEST is unset, skip this test driver altogether.
    return
}

torture-init
set-torture-options $DEC_TORTURE_OPTIONS

note "Using tests in $testdir"
dfp-run-tests [lsort [glob -nocomplain $testdir/*.decTest]]
unset testdir

torture-finish
